package 多线程.并发编程;

import java.util.concurrent.locks.LockSupport;

public class 线程六种状态 {

}
我们先来看看操作系统中的线程状态转换。在操作系统中，线程被视为轻量级的进程，所以线程状态其实和进程状态是一致的。

操作系统的线程主要有以下三个状态：

·就绪状态(ready)：线程正在等待使用 CPU，经调度程序调用之后进入 running 状态。
·执行状态(running)：线程正在使用 CPU。
·等待状态(waiting): 线程经过等待事件的调用或者正在等待其他资源（如 I/O）。

然后我们来看 Java 线程的 6 个状态：
// Thread.State 源码
public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

一、NEW
处于 NEW 状态的线程此时尚未启动。这里的尚未启动指的是还没调用 Thread 实例的start()方法。


private void testStateNew() {
    Thread thread = new Thread(() -> {});
    System.out.println(thread.getState()); // 输出 NEW
}
从上面可以看出，只是创建了线程而并没有调用 start 方法，此时线程处于 NEW 状态。

#关于 start 的两个引申问题
反复调用同一个线程的 start 方法是否可行？
假如一个线程执行完毕（此时处于 TERMINATED 状态），再次调用这个线程的 start 方法是否可行？
要分析这两个问题，我们先来看看start()的源码：

// 使用synchronized关键字保证这个方法是线程安全的
public synchronized void start() {
    // threadStatus != 0 表示这个线程已经被启动过或已经结束了
    // 如果试图再次启动这个线程，就会抛出IllegalThreadStateException异常
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    // 将这个线程添加到当前线程的线程组中
    group.add(this);

    // 声明一个变量，用于记录线程是否启动成功
    boolean started = false;
    try {
        // 使用native方法启动这个线程
        start0();
        // 如果没有抛出异常，那么started被设为true，表示线程启动成功
        started = true;
    } finally {
        // 在finally语句块中，无论try语句块中的代码是否抛出异常，都会执行
        try {
            // 如果线程没有启动成功，就从线程组中移除这个线程
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            // 如果在移除线程的过程中发生了异常，我们选择忽略这个异常
        }
    }
}
可以看到，在start()内部，有一个 threadStatus 变量。如果它不等于 0，调用start()会直接抛出异常。

接着往下看，有一个 native 的 start0() 方法。这个方法并没有对threadStatus进行处理。到这里我们仿佛拿这个 threadStatus 没辙了，通过 debug 再看一下:


@Test
public void testStartMethod() {
    Thread thread = new Thread(() -> {});
    thread.start(); // 第一次调用
    thread.start(); // 第二次调用
}
在 start 方法内部的最开始打断点：

第一次调用时 threadStatus 的值是 0。
第二次调用时 threadStatus 的值不为 0。
查看当前线程状态的源码：

// Thread.getState方法源码：
public State getState() {
    // get current thread state
    return sun.misc.VM.toThreadState(threadStatus);
}

// sun.misc.VM 源码：
public static State toThreadState(int var0) {
    if ((var0 & 4) != 0) {// 如果线程的状态值和4做位与操作结果不为0，线程处于RUNNABLE状态。
        return State.RUNNABLE;
    } else if ((var0 & 1024) != 0) {// 如果线程的状态值和1024做位与操作结果不为0，线程处于BLOCKED状态。
        return State.BLOCKED;
    } else if ((var0 & 16) != 0) {// 如果线程的状态值和16做位与操作结果不为0，线程处于WAITING状态。
        return State.WAITING;
    } else if ((var0 & 32) != 0) {// 如果线程的状态值和32做位与操作结果不为0，线程处于TIMED_WAITING状态。
        return State.TIMED_WAITING;
    } else if ((var0 & 2) != 0) {// 如果线程的状态值和2做位与操作结果不为0，线程处于TERMINATED状态。
        return State.TERMINATED;
    } else {// 最后，如果线程的状态值和1做位与操作结果为0，线程处于NEW状态，否则线程处于RUNNABLE状态。
        return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
    }
}
还记得我们引申的两个问题吗？

反复调用同一个线程的 start 方法是否可行？
假如一个线程执行完毕（此时处于 TERMINATED 状态），再次调用这个线程的 start 方法是否可行？
结合上面的源码可以得到的答案是：
1.都不行，在调用 start 之后，threadStatus 的值会改变（threadStatus !=0），再次调用 start 方法会抛出 IllegalThreadStateException 异常。
2.threadStatus 为 2 代表当前线程状态为 TERMINATED（下面会讲）。

二、RUNNABLE
表示当前线程正在运行中。处于 RUNNABLE 状态的线程在 Java 虚拟机中运行，也有可能在等待 CPU 分配资源。

我们来看看 Thread 源码里对 RUNNABLE 状态的定义：
 Thread state for a runnable thread.  A thread in the runnable state is executing in the Java virtual machine but it may
 be waiting for other resources from the operating system such as processor.
 
意思大家应该都能看得懂，不懂翻译一下（其实前面已经翻译过了）。

也就是说，Java 线程的RUNNABLE状态其实包括了操作系统线程的ready和running两个状态。

三、BLOCKED
阻塞状态。处于 BLOCKED 状态的线程正等待锁（锁会在后面细讲）的释放以进入同步区。我们用 BLOCKED 状态举个生活中的例子：
假如今天你下班后准备去食堂吃饭。你来到食堂仅有的一个窗口，发现前面已经有个人在窗口前了，此时你必须得等前面的人从窗口离开才行。
假设你是线程 t2，你前面的那个人是线程 t1。此时 t1 占有了锁（食堂唯一的窗口），t2 正在等待锁的释放，所以此时 t2 就处于 BLOCKED 状态。

四、WAITING
等待状态。处于等待状态的线程变成 RUNNABLE 状态需要其他线程唤醒。

调用下面这 3 个方法会使线程进入等待状态：

·Object.wait()：使当前线程处于等待状态直到另一个线程唤醒它；
·Thread.join()：等待线程执行完毕，底层调用的是 Object 的 wait 方法；
·LockSupport.park()：除非获得调用许可，否则禁用当前线程进行线程调度。LockSupport 我们在后面会细讲。

我们延续上面的例子继续解释一下 WAITING 状态：

你等了好几分钟，终于轮到你了，突然你们有一个“不懂事”的经理来了。你看到他你就有一种不祥的预感，果然，他是来找你的。
他把你拉到一旁叫你待会儿再吃饭，说他下午要去作报告，赶紧来找你了解一下项目的情况。你心里虽然有一万个不愿意但是你还是从食堂窗口走开了。
此时，假设你还是线程 t2，你的经理是线程 t1。虽然你此时都占有锁（窗口）了，“不速之客”来了你还是得释放掉锁。此时你 t2 的状态就是 WAITING。然后经理 t1 获得
锁，进入 RUNNABLE 状态。

要是经理 t1 不主动唤醒你 t2（notify、notifyAll..），可以说你 t2 只能一直等待了。

五、TIMED_WAITING
超时等待状态。线程等待一个具体的时间，时间到后会被自动唤醒。

调用如下方法会使线程进入超时等待状态：
·Thread.sleep(long millis)：使当前线程睡眠指定时间；
·Object.wait(long timeout)：线程休眠指定时间，等待期间可以通过notify()/notifyAll()唤醒；
·Thread.join(long millis)：等待当前线程最多执行 millis 毫秒，如果 millis 为 0，则会一直执行；
·LockSupport.parkNanos(long nanos)： 除非获得调用许可，否则禁用当前线程进行线程调度指定时间；LockSupport 我们在后面会细讲；
·LockSupport.parkUntil(long deadline)：同上，也是禁止线程进行调度指定时间；

我们继续延续上面的例子来解释一下 TIMED_WAITING 状态：

到了第二天中午，又到了饭点，你还是到了窗口前。突然间想起你的同事叫你等他一起，他说让你等他十分钟他改个 bug。

好吧，那就等等吧，你就离开了窗口。很快十分钟过去了，你见他还没来，你想都等了这么久了还不来，那你还是先去吃饭好了。

这时你还是线程 t1，你改 bug 的同事是线程 t2。t2 让 t1 等待了指定时间，此时 t1 等待期间就属于 TIMED_WATING 状态。

t1 等待 10 分钟后，就自动唤醒，拥有了去争夺锁的资格。

六、TERMINATED
终止状态。此时线程已执行完毕。

#线程状态的转换
根据上面关于线程状态的介绍我们可以得到下面的线程状态转换图：
                                                  NEW
                                                   |
   ┌─────────────── 获取琐成功 ────────────┐   Thread.start()
   |                                     ↓         ↓
BLOCKED ←—————————— 等待琐 —————————— RUNNABLE(包括ready和runnable状态) ─────────→ TERMINATED
                                         ↑      |      |      ↑
          Object.notify()                |      |      |      |      Object.notify()
   ┌────→ Object.notifyAll()      ───────┘      |      |      └───── Object.notifyAll()    ←──────────┐
   |     LockSupport.unpark(Thread)             |      |             LockSupport.unpark(Thread)       |
WAITING                                         |      |                  等待时间到                       
   ↑        Object.wait()                       |      |                                         TIMED_WAITING     
   └─────── Thread.join() ←─────────────────────┘      |            Thread.sleep(long)                ↑
         LockSupport.park()                            |            Object.wait(long)                 |
                                                       └─────────→  Thread.join(long)  ───────────────┘
                                                                    LockSupport.parkNanos()
                                                                    LockSupport.parkUntil()


七、BLOCKED 与 RUNNABLE 状态的转换
我们在上面说过：处于 BLOCKED 状态的线程在等待锁的释放。假如这里有两个线程 a 和 b，a 线程提前获得了锁并暂未释放锁，此时 b 就处于 BLOCKED 状态。我们来看一个例子：

@Test
public void blockedTest() {
    Thread a = new Thread(new Runnable() {
        @Override
        public void run() {
            testMethod();
        }
    }, "a");

    Thread b = new Thread(new Runnable() {
        @Override
        public void run() {
            testMethod();
        }
    }, "b");

    a.start();
    b.start();

    System.out.println(a.getName() + ":" + a.getState()); // 输出？
    System.out.println(b.getName() + ":" + b.getState()); // 输出？
}

// 同步方法争夺锁
private synchronized void testMethod() {
    try {
        Thread.sleep(2000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
初看之下，大家可能会觉得线程 a 会先调用同步方法，同步方法内又调用了Thread.sleep()方法，必然会输出 TIMED_WAITING，而线程 b 因为等待线程 a 释放
锁所以必然会输出 BLOCKED。

其实不然，有两点需要值得大家注意：
·一是在测试方法blockedTest()内还有一个 main 线程
·二是启动线程后执行 run 方法还是需要消耗一定时间的。

测试方法的 main 线程只保证了 a，b 两个线程调用 start 方法（转化为 RUNNABLE 状态），如果 CPU 执行效率高一点，还没等两个线程真正开始争夺锁，就已经
打印此时两个线程的状态（RUNNABLE）了。

当然，如果 CPU 执行效率低一点，其中某个线程也是可能打印出 BLOCKED 状态的（此时两个线程已经开始争夺锁了）。

八、WAITING 状态与 RUNNABLE 状态的转换
根据转换图我们知道有 3 个方法可以使线程从 RUNNABLE 状态转为 WAITING 状态。我们主要介绍下Object.wait()和Thread.join()。

#Object.wait()
调用wait()方法前线程必须持有对象的锁。

线程调用wait()方法时，会释放当前的锁，直到有其他线程调用notify()/notifyAll()方法唤醒等待锁的线程。

需要注意的是，其他线程调用notify()方法只会唤醒单个等待锁的线程，如有有多个线程都在等待这个锁的话不一定会唤醒到之前调用wait()方法的线程。

同样，调用notifyAll()方法唤醒所有等待锁的线程之后，也不一定会马上把时间片分给刚才放弃锁的那个线程，具体要看系统的调度。

#Thread.join()
调用join()方法，会一直等待这个线程执行完毕（转换为 TERMINATED 状态）。

我们再把上面的例子线程启动那里改变一下：


public void blockedTest() {
    ······
    a.start();
    a.join();
    b.start();
    System.out.println(a.getName() + ":" + a.getState()); // 输出 TERMINATED
    System.out.println(b.getName() + ":" + b.getState());
}
要是没有调用 join 方法，main 线程不管 a 线程是否执行完毕都会继续往下走。

a 线程启动之后马上调用了 join 方法，这里 main 线程就会等到 a 线程执行完毕，所以这里 a 线程打印的状态固定是TERMINATED。

至于 b 线程的状态，有可能打印 RUNNABLE（尚未进入同步方法），也有可能打印 TIMED_WAITING（进入了同步方法）。
a